/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "quakedef.h"
#include "particles.h"

#include <smmintrin.h>
#include <xmmintrin.h>

struct partpolyvert_t
{
	float Origin[3];
	int ColorLo;	// really unsigned but this way so we can stream it
	int ColorHi;	// really unsigned but this way so we can stream it
	float ColorTime;
};


// this should be some multiple of MAX_PARTICLES
#define MAX_BUFFER_PARTICLES	16384

partpolyvert_t *d3d_DrawParticles = NULL;

int d3d_FirstParticle = 0;
int d3d_NumParticles = 0;

ID3D10Buffer *d3d_ParticleVertexes = NULL;
QSHADER d3d_PartRoundShader;
QSHADER d3d_PartSquareShader;


class CPartHandler : public CD3DHandler
{
public:
	void OnCreateDevice (void)
	{
		// create our shaders
		d3d_PartRoundShader.CreateShaders (IDR_PARTICLESHADER, "ParticleCommonVS", "ParticleCommonGS", "ParticleRoundPS", INPUT_LAYOUT_PART);
		d3d_PartSquareShader.CreateShaders (IDR_PARTICLESHADER, "ParticleCommonVS", "ParticleCommonGS", "ParticleSquarePS", INPUT_LAYOUT_PART);

		// create our vertex buffer
		D3D10_BUFFER_DESC vbDesc = {
			sizeof (partpolyvert_t) * MAX_BUFFER_PARTICLES,
			D3D10_USAGE_DYNAMIC,
			D3D10_BIND_VERTEX_BUFFER,
			D3D10_CPU_ACCESS_WRITE,
			0
		};

		d3d_Device->CreateBuffer (&vbDesc, NULL, &d3d_ParticleVertexes);
	}

	void OnReleaseDevice (void)
	{
		SAFE_RELEASE (d3d_ParticleVertexes);
	}

	void OnModeChange (void)
	{
		UINT stride = sizeof (partpolyvert_t);
		UINT offset = 0;

		d3d_Device->IASetVertexBuffers (BUFFER_SLOT_PARTICLES, 1, &d3d_ParticleVertexes, &stride, &offset);
	}
} d3d_PartHandler;


void R_BeginParticles (void)
{
	d3d_PartRoundShader.Bind ();
	d3d_Device->IASetPrimitiveTopology (D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);

	// setup
	d3d_DrawParticles = NULL;
	d3d_NumParticles = 0;
}


void R_EndParticles (void)
{
	if (d3d_DrawParticles)
	{
		d3d_ParticleVertexes->Unmap ();
		d3d_DrawParticles = NULL;
	}

	if (d3d_NumParticles)
	{
		d3d_Device->Draw (d3d_NumParticles, d3d_FirstParticle);
		c_draw_call++;

		// advance the batch
		d3d_FirstParticle += d3d_NumParticles;
		d3d_NumParticles = 0;
	}
}


void R_DrawParticles (emitter_t *e)
{
	// don't bother if nothing is active
	if (!e->particles) return;
	if (!e->numparticles) return;

	D3D10_MAP MapType = D3D10_MAP_WRITE_NO_OVERWRITE;

	if (d3d_FirstParticle + d3d_NumParticles + e->numparticles >= MAX_BUFFER_PARTICLES)
	{
		R_EndParticles ();
		MapType = D3D10_MAP_WRITE_DISCARD;
		d3d_FirstParticle = 0;
	}

	if (!d3d_DrawParticles)
	{
		if (FAILED (d3d_ParticleVertexes->Map (MapType, 0, (void **) &d3d_DrawParticles)))
			return;
		else d3d_DrawParticles += d3d_FirstParticle;
	}

	partpolyvert_t *verts = d3d_DrawParticles;
	float etime = cl.time - e->time;

	// this aspect of particle acceleration can be offloaded per-emitter
	float eAccel[3] = {
		(e->dvel[0] + e->grav[0] * sv_gravity->value * 0.05f) * etime,
		(e->dvel[1] + e->grav[1] * sv_gravity->value * 0.05f) * etime,
		(e->dvel[2] + e->grav[2] * sv_gravity->value * 0.05f) * etime
	};

	for (particle_t *p = e->particles; p; p = p->next)
	{
		// catch dead particles
		if (p->die < cl.time) continue;

		// update origin on the CPU; this is a little more work here in exchange for a slimmer vertex
		// (some of it can be offloaded per-emitter too...) - it balances out...
		float neworg[] = {
			p->org[0] + (p->vel[0] + eAccel[0]) * etime,
			p->org[1] + (p->vel[1] + eAccel[1]) * etime,
			p->org[2] + (p->vel[2] + eAccel[2]) * etime,
		};

		// write it out
		_mm_stream_si32 (&((int *) verts->Origin)[0], ((int *) neworg)[0]);
		_mm_stream_si32 (&((int *) verts->Origin)[1], ((int *) neworg)[1]);
		_mm_stream_si32 (&((int *) verts->Origin)[2], ((int *) neworg)[2]);

		// and write out the colour parms so that we can interpolate them on the GPU
		_mm_stream_si32 (&verts->ColorLo, (int) p->ColorLo);
		_mm_stream_si32 (&verts->ColorHi, (int) p->ColorHi);
		_mm_stream_si32 ((int *) &verts->ColorTime, *((int *) &p->ColorTime));

		// handle special flags
		if (p->flags & PF_KILL) p->die = -1;
		if (p->flags & PF_NEVERDIE) p->die = cl.time + 666;

		// don't move these up to the loop because there's a continue...
		verts++;
		d3d_NumParticles++;
	}

	// advance to next batch
	d3d_DrawParticles = verts;
}

